﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using XNOgre.Scene;
using XNOgre.Core;

namespace XNOgre.Graphics
{
    /// <summary>
    /// A material is a set of properties which can be associated with an Entity to provide additional rendering information and overrides to how the Entity is rendered.
    /// A material can be defined either through code or JSON script, it is based off the script language from the OGRE engine <see cref=">http://www.ogre3d.org/docs/manual/manual_14.html#Material-Scripts"/>
    /// <remarks>
    /// Attribute names, while following OGRE's title, use a different format. PascalCase is used, and underscores are ommitted, thus
    /// lod_strategy becomes LodStrategy
    /// </remarks>
    /// </summary>
    public class Material : ICloneable, IDisposable
    {
        private static int UnnamedMaterialCounter = 0;

        public Material()
            : this(XNOgre.Core.Root.GenerateName(typeof(Material)))
        { }

        public Material(string name)
        {
            Name = name;
            SetDefaults();
        }
        private void SetDefaults()
        {
            ReceiveShadows = false;
            Techniques = new Technique[] { new Technique() };
        }
        public string Name { get; set; }

        /// <summary>
        /// Controls whether objects using this material can have shadows cast upon them.
        /// </summary>
        public bool ReceiveShadows { get; set; }
        public bool CastsShadows { get; set; }

        public Technique CurrentTechnique
        {
            get
            {
                return Techniques[0];
            }
        }

        public Technique[] Techniques { get; set; }


        /// <summary>
        /// Gets a copy of a basic material with Dynamic Lighting enabled.
        /// </summary>
        public static Material BaseWhite
        {
            get
            {
                TextureUnit tu = new TextureUnit();
                Pass p = new Pass();
                p.TextureUnit = tu;

                Technique t = new Technique();
                t.Passes = new Pass[] { p };

                Material mat = new Material("BaseWhite");
                mat.Techniques = new Technique[] { t };

                return mat;
            }
        }
        /// <summary>
        /// Gets a copy of a basic material with no Dynamic Lighting (Meaning it will be fully lit when rendering)
        /// </summary>
        public static Material BaseWhiteNoLighting
        {
            get
            {
                Material mat = BaseWhite;
                mat.Techniques[0].Passes[0].Lighting = false;
                return mat;
            }
        }

        public object Clone()
        {
            Material retVal = new Material(this.Name);
            retVal.CastsShadows = this.CastsShadows;
            retVal.ReceiveShadows = this.ReceiveShadows;

            Technique[] newTecs = new Technique[Techniques.Length];

            for (int i = 0; i < Techniques.Length; i++)
            {
                newTecs[i] = (Technique)Techniques[i].Clone();
            }

            retVal.Techniques = newTecs;

            return retVal;
        }

        public void Dispose()
        {
            for (int i = 0; i < Techniques.Length; i++)
			{
                Techniques[i].Dispose();
                Techniques[i] = null;
			}
            Techniques = null;
        }
    }
    /// <summary>
    /// Encapsulates a single method of rendering an object.
    /// </summary>
    public class Technique : ICloneable,IDisposable
    {
        public Technique()
        {
            //Set defaults:
            LodIndex = 0;

            Passes = new Pass[] { new Pass() };
        }
        /// <summary>
        /// Sets the level-of-detail (LOD) index this technique belongs to
        /// Default is 0
        /// <remarks>
        /// It is recommended to leave gaps between LOD indexes
        /// </remarks>
        /// </summary>
        public int LodIndex { get; set; }

        public Pass[] Passes { get; set; }

        public object Clone()
        {
            Technique retVal = new Technique();
            Pass[] newPasses = new Pass[this.Passes.Length];
            for (int i = 0; i < this.Passes.Length; i++)
            {
                newPasses[i] = (Pass)this.Passes[i].Clone();
            }
            return retVal;
        }

        public void Dispose()
        {
            for (int i = 0; i < Passes.Length; i++)
            {
                Passes[i].Dispose();
                Passes[i] = null;
            }
            Passes = null;
        }
    }
    /// <summary>
    /// A pass is a single render of the geometry in question;
    /// a single call to be rendered with certain properties. A technique can have between one and 16 passes,
    /// although clearly the more passes you use, the more expensive the technique will be to render.
    /// </summary>
    public class Pass : ICloneable, IDisposable
    {
        public Pass()
        {
            //Default values
            Alpha = 1.0f;
            Ambient = Vector3.One;
            Diffuse = Vector3.One;
            Specular = Vector3.Zero;
            SpecularPower = 0;
            Lighting = true;
            MaxLights = 8;

            DepthStencil = DepthStencilState.Default;
            RasterState = new RasterizerState();
            BlendState = new Microsoft.Xna.Framework.Graphics.BlendState();

            Shader = null;
            TextureUnit = new TextureUnit();
        }
        /// <summary>
        /// Sets a custom Effect and supplied parameters to use as opposed to Engine selected ones.
        /// <!--If this value is set, many attributes of this pass will be ignored and will rely on the Parameters of the effect-->
        /// </summary>
        public Shader Shader { get; set; }
        /// <summary>
        /// The ambient color reflectance properties of this pass.
        /// </summary>
        public Vector3 Ambient { get; set; }

        public float Alpha { get; set; }
        /// <summary>
        /// Sets the diffuse color reflectance of this pass.
        /// </summary>
        public Vector3 Diffuse { get; set; }

        /// <summary>
        /// Sets the amount of self-illumination an object has.
        /// </summary>
        /// <remarks>If an object is self-illumination, it does not need external sources to light it, ambient or otherwise. It's like an object has it's own personal ambient light.</remarks>
        public Vector3 Emissive { get; set; }

        /// <summary>
        /// Sets the specular color reflectance properties of this pass.
        /// Coupled with SpecularPower
        /// </summary>
        public Vector3 Specular { get; set; }

        /// <summary>
        /// Sets Specular Power
        /// Coupled with Specular
        /// </summary>
        public float SpecularPower { get; set; }

        /// <summary>
        /// Sets whether or not dynamic lighting is turned on for this pass.
        /// If lighting is turned off (false), all objects rendered using the pass will be fully lit.
        /// </summary>
        public bool Lighting { get; set; }

        /// <summary>
        /// Tells the pass whether it should override the scene fog settings. 
        /// If FogOverride.Enabled is set to true, the parameters of FogOverride will be used when rendering fog on this pass,
        /// else it will use the settings from SceneManager
        /// </summary>
        public FogSettings FogOverride { get; set; }

        /// <summary>
        /// Sets the maximum number of lights which will be considered for use with this pass.
        /// <remarks>
        /// 8 is a System max, but a lower number can be set.
        /// </remarks>
        /// </summary>
        public int MaxLights { get; set; }

        /// <summary>
        /// Returns true if this pass isn't fully opaque.
        /// Determined by if the Alpha isn't 1.0f
        /// </summary>
        public bool IsTransparent
        {
            get
            {
                return Alpha != 1;
            }
        }

        public TextureUnit TextureUnit { get; set; }

        public DepthStencilState DepthStencil { get; set; }
        public RasterizerState RasterState { get; set; }
        public BlendState BlendState { get; set; }


        public object Clone()
        {
            Pass retVal = new Pass();
            //structs are transferred by value
            retVal.Ambient = this.Ambient;
            retVal.Diffuse = this.Diffuse;
            retVal.Specular = this.Specular;
            retVal.Emissive = this.Emissive;
            retVal.SpecularPower = this.SpecularPower;
            retVal.MaxLights = this.MaxLights;
            retVal.Lighting = this.Lighting;

            retVal.FogOverride = new FogSettings()
            {
                FogColor = this.FogOverride.FogColor,
                FogEnabled = this.FogOverride.FogEnabled,
                FogEnd = this.FogOverride.FogEnd,
                FogStart = this.FogOverride.FogStart
            };

            retVal.DepthStencil = new DepthStencilState()
            {
                CounterClockwiseStencilDepthBufferFail = this.DepthStencil.CounterClockwiseStencilDepthBufferFail,
                CounterClockwiseStencilFail = this.DepthStencil.CounterClockwiseStencilFail,
                CounterClockwiseStencilFunction = this.DepthStencil.CounterClockwiseStencilFunction,
                CounterClockwiseStencilPass = this.DepthStencil.CounterClockwiseStencilPass,
                DepthBufferEnable = this.DepthStencil.DepthBufferEnable,
                DepthBufferFunction = this.DepthStencil.DepthBufferFunction,
                DepthBufferWriteEnable = this.DepthStencil.DepthBufferWriteEnable,
                Name = this.DepthStencil.Name,
                ReferenceStencil = this.DepthStencil.ReferenceStencil,
                StencilDepthBufferFail = this.DepthStencil.StencilDepthBufferFail,
                StencilEnable = this.DepthStencil.StencilEnable,
                StencilFail = this.DepthStencil.StencilFail,
                StencilFunction = this.DepthStencil.StencilFunction,
                StencilMask = this.DepthStencil.StencilMask,
                StencilPass = this.DepthStencil.StencilPass,
                StencilWriteMask = this.DepthStencil.StencilWriteMask,
                TwoSidedStencilMode = this.DepthStencil.TwoSidedStencilMode
            };

            retVal.BlendState = new BlendState()
            {
                AlphaBlendFunction = this.BlendState.AlphaBlendFunction,
                AlphaDestinationBlend = this.BlendState.AlphaDestinationBlend,
                AlphaSourceBlend = this.BlendState.AlphaSourceBlend,
                BlendFactor = this.BlendState.BlendFactor,
                ColorBlendFunction = this.BlendState.ColorBlendFunction,
                ColorDestinationBlend = this.BlendState.ColorDestinationBlend,
                ColorSourceBlend = this.BlendState.ColorSourceBlend,
                ColorWriteChannels = this.BlendState.ColorWriteChannels,
                ColorWriteChannels1 = this.BlendState.ColorWriteChannels1,
                ColorWriteChannels2 = this.BlendState.ColorWriteChannels2,
                ColorWriteChannels3 = this.BlendState.ColorWriteChannels3,
                MultiSampleMask = this.BlendState.MultiSampleMask,
                Name = this.BlendState.Name
            };

            retVal.RasterState = new RasterizerState()
            {
                CullMode = this.RasterState.CullMode,
                DepthBias = this.RasterState.DepthBias,
                FillMode = this.RasterState.FillMode,
                MultiSampleAntiAlias = this.RasterState.MultiSampleAntiAlias,
                Name = this.RasterState.Name,
                ScissorTestEnable = this.RasterState.ScissorTestEnable,
                SlopeScaleDepthBias = this.RasterState.SlopeScaleDepthBias
            };

            if (this.Shader != null)
            {
                retVal.Shader = (Shader)this.Shader.Clone();
            }
            retVal.TextureUnit = (TextureUnit)this.TextureUnit.Clone();

            return retVal;
        }

        public void Dispose()
        {
            if (Shader != null)
            {
                Shader.Dispose();
                Shader = null;
            }
            if (TextureUnit != null)
            {
                TextureUnit.Dispose();
                TextureUnit = null;
            }

            this.BlendState = new BlendState();
            this.RasterState = new RasterizerState();
            this.DepthStencil = DepthStencilState.Default;

        }
    }
    public class TextureUnit : ICloneable, IDisposable
    {
        public TextureUnit()
        {
            //Defaults:
            TexAddressMode = TextureAddressMode.Wrap;
            EnvMap = null;
            Scroll = Vector2.Zero;
            ScrollAnim = Vector2.Zero;
            Filtering = TextureFilter.Linear;
            MaxAnisotropy = 1;
        }
        /// <summary>
        /// Sets the name of the static texture image this layer will use.
        /// 
        /// <remarks>
        /// In XNOgre, the texture provided must have been processed by the content-pipeline. Do not include the file extension!
        /// </remarks>
        /// </summary>
        public string TextureName { get; set; }

        private Texture2D texture;
        public Texture2D Texture
        {
            get
            {
                if (texture == null && !String.IsNullOrEmpty(TextureName))
                {
                    texture = Root.Instance.Content.Load<Texture2D>(TextureName);
                }

                return texture;
            }
            set
            {
                texture = value;
            }
        }
        /// <summary>
        /// Sets the type of texture filtering used when magnifying or minifying a texture.
        /// </summary>
        public TextureFilter Filtering { get; set; }

        /// <summary>
        /// Sets the bias value applied to the mipmapping calculation, 
        /// thus allowing you to alter the decision of which level of detail 
        /// of the texture to use at any distance.
        /// </summary>
        public float MipMapBias { get; set; }

        /// <summary>
        /// Sets the maximum degree of anisotropy that the renderer will try to compensate for when filtering textures.
        /// </summary>
        public int MaxAnisotropy { get; set; }

        /// <summary>
        /// Gets/Sets the LOD Index of the largest map to use.
        /// </summary>
        public int MaxMipLevel { get; set; }

        internal SamplerState Sampler
        {
            get
            {
                SamplerState ss = new SamplerState();
                ss.AddressU = ss.AddressV = ss.AddressW = TexAddressMode;
                ss.Filter = Filtering;
                ss.MaxAnisotropy = MaxAnisotropy;
                ss.MipMapLevelOfDetailBias = MipMapBias;
                ss.MaxMipLevel = MaxMipLevel;
                return ss;
            }
        }
        /// <summary>
        /// An array of images to be flipped through to create the appearence of movement.
        /// Coupled with AnimTextureSpeed
        /// </summary>
        public string[] AnimTextures { get; set; }
        /// <summary>
        /// The length in seconds between flipping images in AnimTexture
        /// </summary>
        public float AnimTextureSpeed { get; set; }

        /// <summary>
        /// An array of textures to make up the faces of a cube.
        /// The order is: front,back,left,right,up,down
        /// Coupled with SeperateUV
        /// </summary>
        public string[] CubicTexture { get; set; }

        /// <summary>
        /// If true, the textures in CubicTexture are kept seperate, with One texture at a time being active, useful for Skyboxes.
        /// If false, the textures in CubicTexture will be combined into a single texture map which is then addressed using 3d texture coordinates (U,V, and W), useful for reflection maps.
        /// Coupled with CubicTexture
        /// </summary>
        public bool SeperateUV { get; set; }

        //Not-Implemented: binding_type, content_type, tex_coord_set

        /// <summary>
        /// Defines what happens when texture coordinates exceed 1.0 for this texture layer
        /// </summary> 
        public TextureAddressMode TexAddressMode { get; set; }

        /// <summary>
        /// The name of a .dss file to create the CubeMap
        /// </summary>
        public string EnvMap { get; set; }

        /// <summary>
        /// A value 0-1 the amount the color map will be wrapped over the texture.
        /// </summary>
        public float EnvMapAmount { get; set; }

        /// <summary>
        /// Static offset for the texture, for animated textures <see cref=">ScrollAnim"/>
        /// </summary>
        public Vector2 Scroll { get; set; }

        /// <summary>
        /// Specifies a speed for the texture to scroll.
        /// </summary>
        public Vector2 ScrollAnim { get; set; }

        //Not-Implemented: rotate_rotate_anim, scale, wave_xform, transform

        public object Clone()
        {
            TextureUnit retVal = new TextureUnit()
            {
                AnimTextureSpeed = this.AnimTextureSpeed,
                EnvMapAmount = this.EnvMapAmount,
                Filtering = this.Filtering,
                MaxAnisotropy = this.MaxAnisotropy,
                MaxMipLevel = this.MaxMipLevel,
                MipMapBias = this.MipMapBias,
                Scroll = this.Scroll,
                ScrollAnim = this.ScrollAnim,
                SeperateUV = this.SeperateUV,
                TexAddressMode = this.TexAddressMode,
                Texture = this.Texture
            };
            if (AnimTextures != null)
            {
                retVal.AnimTextures = (string[])this.AnimTextures.Clone();
            }
            retVal.AnimTextureSpeed = this.AnimTextureSpeed;
            if (CubicTexture != null)
            {
                retVal.CubicTexture = (string[])CubicTexture.Clone();
            }
            if (EnvMap != null)
            {
                retVal.EnvMap = (string)this.EnvMap.Clone();
            }
            return retVal;

            //sampler
        }

        public void Dispose()
        {
            if (Sampler != null && Sampler.IsDisposed == false)
            {
                Sampler.Dispose();
            }
        }
    }
        public class Shader : ICloneable, IDisposable
        {
            public Shader(Effect effect, params ShaderParameter[] parms)
            {
                this.Effect = effect;

                Parameters = new Dictionary<string, ShaderParameter>();
                if (parms != null && parms.Length > 0)
                {
                    foreach (var parm in parms)
                    {
                        Parameters.Add(parm.ParamName, parm);
                    }
                }
            }
            public Effect Effect { get; set; }

            public Dictionary<string, ShaderParameter> Parameters { get; private set; }

            public object Clone()
            {
                Dictionary<string, ShaderParameter> shaderParms = new Dictionary<string, ShaderParameter>();
                foreach (var key in this.Parameters.Keys)
                {
                    shaderParms.Add(key, this.Parameters[key]);
                }

                Shader retVal = new Shader(this.Effect, null);
                retVal.Parameters = shaderParms;
                return retVal;
            }

            public void Dispose()
            {
                if (Effect != null)
                {
                    Effect.Dispose();
                }
                Parameters.Clear();
            }
        }
        public class ShaderParameter : ICloneable
        {
            bool isEngineParam;
            string paramName;
            object value;

            /// <summary>
            /// Assigns a shader parameter to the value supplied by the user
            /// </summary>
            /// <param name="param">Name of the parameter in the .fx file to set</param>
            /// <param name="userValue">The value to set it to</param>
            public ShaderParameter(string param, object userValue)
            {
                this.paramName = param;
                this.isEngineParam = false;
                this.value = userValue;
            }

            /// <summary>
            /// Assigns a shader parameter to the value supplied by the engine
            /// </summary>
            /// <param name="param">Name of the parameter in the .fx file to set</param>
            /// <param name="engineValue">The value to be obtained from the engine</param>
            public ShaderParameter(string param, GpuProgramParameter engineValue)
            {
                this.paramName = param;
                this.isEngineParam = true;
                this.value = engineValue;
            }

            /// <summary>
            /// Gets whether the value should be filled in from the engine.
            /// </summary>
            public bool IsEngineParam
            {
                get { return isEngineParam; }
            }
            /// <summary>
            /// Gets the name of the parameter to set
            /// </summary>
            public string ParamName
            {
                get
                {
                    return paramName;
                }
            }
            /// <summary>
            /// Gets the value to set the parameter to.
            /// If it is an engine value, this property will return a <see cref="GpuProgramParameter"/>,
            /// else it will return the user supplied value
            /// </summary>
            public object Value
            {
                get { return value; }
                set
                {
                    this.value = value;
                    if (this.value is GpuProgramParameter)
                    {
                        isEngineParam = true;
                    }
                    else
                    {
                        isEngineParam = false;
                    }
                }
            }

            /// <summary>
            /// Gets/Sets the index if the value you're attempting to access is an array
            /// </summary>
            public int AutoConstant { get; set; }

            /// <summary>
            /// Gets/Sets the amount of values you want returned when requestion a GpuProgramParameter array
            /// <remarks>Leaving this parameter 0 will return all available items</remarks>
            /// </summary>
            public int AutoArrayCount { get; set; }

            public object Clone()
            {
                ShaderParameter retVal = new ShaderParameter(this.paramName, this.value)
                {
                    isEngineParam = this.isEngineParam,
                    value = this.value,
                    paramName = this.paramName,
                     AutoConstant = this.AutoConstant,
                };
                return retVal;
            }
        }
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
        public class NotYetUsed : Attribute
        {
            string reason;

            public NotYetUsed(string reason)
            {
                this.reason = reason;
            }
            public NotYetUsed()
            { }
        }
    }
